/**********************************************************************

  Audacity: A Digital Audio Editor

  Effect.cpp

  Dominic Mazzoni
  Vaughan Johnson
  Martyn Shaw

*******************************************************************//**

\class Effect
\brief Base class for many of the effects in Audacity.

*//*******************************************************************/
#include "Effect.h"
#include "EffectOutputTracks.h"

#include <algorithm>

#include <wx/defs.h>

#include "BasicUI.h"
#include "ConfigInterface.h"
#include "ProjectNumericFormats.h"
#include "ShuttleAutomation.h"
#include "ViewInfo.h"
#include "WaveTrack.h"
#include "wxFileNameWrapper.h"
#include "NumericConverterFormats.h"

Effect::Effect()
{
}

Effect::~Effect()
{
}

// ComponentInterface implementation

PluginPath Effect::GetPath() const
{
    return BUILTIN_EFFECT_PREFIX + GetSymbol().Internal();
}

ComponentInterfaceSymbol Effect::GetSymbol() const
{
    return {};
}

VendorSymbol Effect::GetVendor() const
{
    return XO("Audacity");
}

wxString Effect::GetVersion() const
{
    return AUDACITY_VERSION_STRING;
}

TranslatableString Effect::GetDescription() const
{
    return {};
}

EffectFamilySymbol Effect::GetFamily() const
{
    // Unusually, the internal and visible strings differ for the built-in
    // effect family.
    return { wxT("Audacity"), XO("Built-in") };
}

bool Effect::IsInteractive() const
{
    return true;
}

bool Effect::IsDefault() const
{
    return true;
}

auto Effect::RealtimeSupport() const -> RealtimeSince
{
    return RealtimeSince::Never;
}

bool Effect::SupportsAutomation() const
{
    return true;
}

const EffectParameterMethods& Effect::Parameters() const
{
    static const CapturedParameters<Effect> empty;
    return empty;
}

bool Effect::VisitSettings(SettingsVisitor& visitor, EffectSettings& settings)
{
    Parameters().Visit(*this, visitor, settings);
    return true;
}

bool Effect::VisitSettings(
    ConstSettingsVisitor& visitor, const EffectSettings& settings) const
{
    Parameters().Visit(*this, visitor, settings);
    return true;
}

bool Effect::SaveSettings(
    const EffectSettings& settings, CommandParameters& parms) const
{
    Parameters().Get(*this, settings, parms);
    return true;
}

bool Effect::LoadSettings(
    const CommandParameters& parms, EffectSettings& settings) const
{
    // The first argument, and with it the const_cast, will disappear when
    // all built-in effects are stateless.
    return Parameters().Set(*const_cast<Effect*>(this), parms, settings);
}

OptionalMessage Effect::LoadUserPreset(
    const RegistryPath& name, EffectSettings& settings) const
{
    // Find one string in the registry and then reinterpret it
    // as complete settings
    wxString parms;
    if (!GetConfig(GetDefinition(), PluginSettings::Private,
                   name, wxT("Parameters"), parms)) {
        return {};
    }

    return LoadSettingsFromString(parms, settings);
}

bool Effect::SaveUserPreset(
    const RegistryPath& name, const EffectSettings& settings) const
{
    // Save all settings as a single string value in the registry
    wxString parms;
    if (!SaveSettingsAsString(settings, parms)) {
        return false;
    }

    return SetConfig(GetDefinition(), PluginSettings::Private,
                     name, wxT("Parameters"), parms);
}

RegistryPaths Effect::GetFactoryPresets() const
{
    return {};
}

OptionalMessage Effect::LoadFactoryPreset(int id, EffectSettings& settings) const
{
    return { nullptr };
}

OptionalMessage Effect::LoadFactoryDefaults(EffectSettings& settings) const
{
    return LoadUserPreset(FactoryDefaultsGroup(), settings);
}

bool Effect::CanExportPresets() const
{
    return true;
}

bool Effect::HasOptions() const
{
    return false;
}

// EffectPlugin implementation

const EffectSettingsManager& Effect::GetDefinition() const
{
    return *this;
}

NumericFormatID Effect::GetSelectionFormat()
{
    if (!IsBatchProcessing() && FindProject()) {
        return ProjectNumericFormats::Get(*FindProject())
               .GetSelectionFormat();
    }
    return NumericConverterFormats::HoursMinsSecondsFormat().Internal();
}

wxString Effect::GetSavedStateGroup()
{
    return wxT("SavedState");
}

// Effect implementation

bool Effect::SaveSettingsAsString(
    const EffectSettings& settings, wxString& parms) const
{
    CommandParameters eap;
    ShuttleGetAutomation S;
    S.mpEap = &eap;
    if (VisitSettings(S, settings)) {
        // got eap value using VisitSettings.
    }
    // Won't be needed in future
    else if (!SaveSettings(settings, eap)) {
        return false;
    }

    return eap.GetParameters(parms);
}

OptionalMessage Effect::LoadSettingsFromString(
    const wxString& parms, EffectSettings& settings) const
{
    // If the string starts with one of certain significant substrings,
    // then the rest of the string is reinterpreted as part of a registry key,
    // and a user or factory preset is then loaded.
    // (Where did these prefixes come from?  See EffectPresetsDialog; and
    // ultimately the uses of it by EffectManager::GetPreset, which is used by
    // the macro management dialog)
    wxString preset = parms;
    OptionalMessage result;
    if (preset.StartsWith(kUserPresetIdent)) {
        preset.Replace(kUserPresetIdent, wxEmptyString, false);
        result = LoadUserPreset(UserPresetsGroup(preset), settings);
    } else if (preset.StartsWith(kFactoryPresetIdent)) {
        preset.Replace(kFactoryPresetIdent, wxEmptyString, false);
        auto presets = GetFactoryPresets();
        result = LoadFactoryPreset(
            make_iterator_range(presets).index(preset), settings);
    } else if (preset.StartsWith(kCurrentSettingsIdent)) {
        preset.Replace(kCurrentSettingsIdent, wxEmptyString, false);
        result = LoadUserPreset(CurrentSettingsGroup(), settings);
    } else if (preset.StartsWith(kFactoryDefaultsIdent)) {
        preset.Replace(kFactoryDefaultsIdent, wxEmptyString, false);
        result = LoadUserPreset(FactoryDefaultsGroup(), settings);
    } else {
        // If the string did not start with any of the significant substrings,
        // then use VisitSettings or LoadSettings to reinterpret it,
        // or use LoadSettings.
        // This interprets what was written by SaveSettings, above.
        CommandParameters eap(parms);
        ShuttleSetAutomation S;
        S.SetForValidating(&eap);
        // VisitSettings returns false if not defined for this effect.
        // To do: fix const_cast in use of VisitSettings
        if (!const_cast<Effect*>(this)->VisitSettings(S, settings)) {
            // the old method...
            if (LoadSettings(eap, settings)) {
                return { nullptr };
            }
        } else if (!S.bOK) {
            result = {};
        } else {
            result = { nullptr };
            S.SetForWriting(&eap);
            const_cast<Effect*>(this)->VisitSettings(S, settings);
        }
    }

    if (!result) {
        using namespace BasicUI;
        ShowMessageBox(
            XO("%s: Could not load settings below. Default settings will be used.\n\n%s")
            .Format(GetName(), preset),
            MessageBoxOptions {}.Caption(GetName()));
        // We are using default settings and we still wish to continue.
        result = { nullptr };
    }
    return result;
}

unsigned Effect::TestUIFlags(unsigned mask)
{
    return mask & mUIFlags;
}

bool Effect::IsBatchProcessing() const
{
    return mIsBatch;
}

void Effect::SetBatchProcessing()
{
    mIsBatch = true;
    // Save effect's internal state in a special registry path
    // just for this purpose
    // If effect is not stateful, this step doesn't really matter, and the
    // settings object is a dummy
    auto dummySettings = MakeSettings();
    SaveUserPreset(GetSavedStateGroup(), dummySettings);
}

void Effect::UnsetBatchProcessing()
{
    mIsBatch = false;
    // Restore effect's internal state from registry
    // If effect is not stateful, this call doesn't really matter, and the
    // settings object is a dummy
    auto dummySettings = MakeSettings();
    // Ignore failure
    (void)LoadUserPreset(GetSavedStateGroup(), dummySettings);
}

bool Effect::Delegate(Effect& delegate, EffectSettings& settings,
                      InstanceFinder finder)
{
    if (!finder) {
        finder = DefaultInstanceFinder(delegate);
    }

    NotifyingSelectedRegion region;
    region.setTimes(mT0, mT1);

    return delegate.DoEffect(settings, finder, mProjectRate, mTracks.get(),
                             mFactory, region, mUIFlags, nullptr);
}

bool Effect::TotalProgress(double frac, const TranslatableString& msg) const
{
    auto updateResult = (mProgress
                         ? mProgress->Poll(frac * 1000, 1000, msg)
                         : BasicUI::ProgressResult::Success);
    return updateResult != BasicUI::ProgressResult::Success;
}

bool Effect::TrackProgress(
    int whichTrack, double frac, const TranslatableString& msg) const
{
    auto updateResult = (mProgress
                         ? mProgress->Poll((whichTrack + frac) * 1000,
                                           (double)std::max(mNumTracks, 1) * 1000, msg)
                         : BasicUI::ProgressResult::Success);
    return updateResult != BasicUI::ProgressResult::Success;
}

bool Effect::TrackGroupProgress(
    int whichGroup, double frac, const TranslatableString& msg) const
{
    auto updateResult = (mProgress
                         ? mProgress->Poll((whichGroup + frac) * 1000,
                                           (double)mNumGroups * 1000, msg)
                         : BasicUI::ProgressResult::Success);
    return updateResult != BasicUI::ProgressResult::Success;
}

void Effect::GetBounds(
    const WaveTrack& track, sampleCount* start, sampleCount* len) const
{
    const auto t0 = std::max(mT0, track.GetStartTime());
    const auto t1 = std::min(mT1, track.GetEndTime());
    if (t1 > t0) {
        *start = track.TimeToLongSamples(t0);
        auto end = track.TimeToLongSamples(t1);
        *len = end - *start;
    } else {
        *start = 0;
        *len = 0;
    }
}

bool Effect::CheckWhetherSkipEffect(const EffectSettings&) const
{
    return false;
}

double Effect::CalcPreviewInputLength(
    const EffectSettings&, double previewLength) const
{
    return previewLength;
}
